﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using GameCamera;

namespace Shape
{
  public struct VertexPositionTextureNormal : IVertexType
  {
    public Vector3 Position;
    public Vector2 texCoord;
    public Vector3 Normal;

    public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
    (
        new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
        new VertexElement(sizeof(float) * 3,VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
        new VertexElement(sizeof(float) * (3 + 2), VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
    );

    VertexDeclaration IVertexType.VertexDeclaration
    {
      get { return VertexPositionTextureNormal.VertexDeclaration; }
    }
  }

  interface IShape
  {
    void drawShape(Camera camera);
    Effect Effect { get; set; }
    bool Rotate { get; set; }
    bool CreateShadowMap { get; set; }
  }

  public abstract class Shape : IShape
  {
    internal Effect effect;
    internal GraphicsDevice graphicd;
    internal int nvertices, nindices;
    internal IndexBuffer ibuffer;
    internal VertexBuffer vbuffer;
    internal VertexPositionTextureNormal[] vertices;
    internal short[] indices;
    internal Vector3 origin;
    internal bool rotating;
    internal Matrix worldMatrix;
    internal float angle;
    internal Texture2D texture;
    internal TextureCube skyBox;
    internal bool createShadowMap;

    public bool CreateShadowMap
    {
      get { return createShadowMap; }
      set { createShadowMap = value; }
    }

    public bool Rotate
    {
      get { return rotating; }
      set { rotating = value; }
    }

    public Effect Effect
    {
      get { return effect; }
      set { this.effect = value; }
    }

    internal virtual void createNormals()
    {
      for (int i = 0; i < vertices.Length; i++)
        vertices[i].Normal = new Vector3(0, 0, 0);

      for (int i = 0; i < indices.Length / 3; i++)
      {
        Vector3 v1 = vertices[indices[i * 3 + 1]].Position - vertices[indices[i * 3]].Position;
        Vector3 v2 = vertices[indices[i * 3]].Position - vertices[indices[i * 3 + 2]].Position;
        Vector3 normal = Vector3.Cross(v1, v2);
        normal.Normalize();
        vertices[indices[i * 3]].Normal += normal;
        vertices[indices[i * 3 + 1]].Normal += normal;
        vertices[indices[i * 3 + 2]].Normal += normal;
      }
    }

    public virtual void rotate()
    {
      const float ROTATION_SPEED = 0.02f;
      angle += ROTATION_SPEED;
      this.worldMatrix = Matrix.CreateTranslation(-origin.X, -origin.Y, -origin.Z) * Matrix.CreateRotationY(angle) *
        Matrix.CreateTranslation(origin.X, origin.Y, origin.Z);
    }

    public virtual void drawShape(Camera camera)
    {
      if (rotating)
        rotate();

      //effect.Parameters["World"].SetValue(worldMatrix);
      effect.Parameters["View"].SetValue(camera.viewMatrix);
      effect.Parameters["Projection"].SetValue(camera.projectionMatrix);
      if (this.texture != null)
      {
        effect.Parameters["t"].SetValue(texture);
      }
      if(skyBox != null)
      {
        effect.Parameters["SkyBoxTexture"].SetValue(skyBox);
      }

      graphicd.SetVertexBuffer(vbuffer);
      graphicd.Indices = ibuffer;

      foreach (EffectPass pass in effect.CurrentTechnique.Passes)
      {
        pass.Apply();
      }

      graphicd.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nvertices, 0, nindices / 3);

    }
  }

  public class Cube : Shape
  {
    float length;

    private void create(Vector3 origin, float length, GraphicsDevice graphics, Effect effect, Texture2D texture, TextureCube skyBox)
    {
      this.createShadowMap = false;
      this.length = length;
      this.origin = origin;
      this.rotating = false;
      this.worldMatrix = Matrix.Identity;
      this.angle = 0.0f;
      this.texture = texture;
      this.skyBox = skyBox;
      graphicd = graphics;
      this.effect = effect;
      nvertices = 36;
      nindices = 36; //6 faces, 2 triangles per face, 3 vertices per triangle
      vbuffer = new VertexBuffer(graphics, typeof(VertexPositionTextureNormal), nvertices, BufferUsage.WriteOnly);
      ibuffer = new IndexBuffer(graphics, IndexElementSize.SixteenBits, nindices, BufferUsage.WriteOnly);
      createIndices();
      createVertices();
      createNormals();
      vbuffer.SetData<VertexPositionTextureNormal>(vertices);
      ibuffer.SetData<short>(indices);
    }

    public Cube(Vector3 origin, float length, GraphicsDevice graphics, Effect effect, Texture2D texture, TextureCube skyBox)
    {
      create(origin, length, graphics, effect, texture, skyBox);
    }

    public Cube(Vector3 origin, float length, GraphicsDevice graphics, Effect basicEffect, Effect lightingEffect, Color color, Texture2D texture, TextureCube skyBox)
    {
      create(origin, length, graphics, basicEffect, texture, skyBox);
    }

    private void createVertices()
    {
      vertices = new VertexPositionTextureNormal[nvertices];
      for (int i = 0; i < vertices.Length; i++ )
      {
        vertices[i].texCoord = new Vector2(0.0f, 0.0f);
        vertices[i].Normal = Vector3.Backward;
      }

      Vector3 topLeftFront = origin + new Vector3(-1.0f, 1.0f, -1.0f) * length;
      Vector3 topLeftBack = origin + new Vector3(-1.0f, 1.0f, 1.0f) * length;
      Vector3 topRightFront = origin + new Vector3(1.0f, 1.0f, -1.0f) * length;
      Vector3 topRightBack = origin + new Vector3(1.0f, 1.0f, 1.0f) * length;

      // Calculate the position of the vertices on the bottom face
      Vector3 btmLeftFront = origin + new Vector3(-1.0f, -1.0f, -1.0f) * length;
      Vector3 btmLeftBack = origin + new Vector3(-1.0f, -1.0f, 1.0f) * length;
      Vector3 btmRightFront = origin + new Vector3(1.0f, -1.0f, -1.0f) * length;
      Vector3 btmRightBack = origin + new Vector3(1.0f, -1.0f, 1.0f) * length;

      // Add the vertices for the FRONT face.

      vertices[0].Position = topLeftFront; vertices[0].texCoord = new Vector2(0.0f, 0.0f);
      vertices[1].Position = btmLeftFront; vertices[1].texCoord = new Vector2(0.0f, 1.0f);
      vertices[2].Position = topRightFront; vertices[2].texCoord = new Vector2(1.0f, 0.0f);
      vertices[3].Position = btmLeftFront; vertices[3].texCoord = new Vector2(0.0f, 1.0f);
      vertices[4].Position = btmRightFront; vertices[4].texCoord = new Vector2(1.0f, 1.0f);
      vertices[5].Position = topRightFront; vertices[5].texCoord = new Vector2(1.0f, 0.0f);



      // Add the vertices for the BACK face.
      vertices[6].Position = topLeftBack; vertices[6].texCoord = new Vector2(0.0f, 0.0f);
      vertices[7].Position = topRightBack; vertices[7].texCoord = new Vector2(0.0f, 1.0f);
      vertices[8].Position = btmLeftBack; vertices[8].texCoord = new Vector2(1.0f, 0.0f);
      vertices[9].Position = btmLeftBack; vertices[9].texCoord = new Vector2(0.0f, 1.0f);
      vertices[10].Position = topRightBack; vertices[10].texCoord = new Vector2(1.0f, 1.0f);
      vertices[11].Position = btmRightBack; vertices[11].texCoord = new Vector2(0.0f, 0.0f);



      // Add the vertices for the TOP face.

      vertices[12].Position = topLeftFront; 
      vertices[13].Position = topRightBack; 
      vertices[14].Position = topLeftBack; 
      vertices[15].Position = topLeftFront;
      vertices[16].Position = topRightFront;
      vertices[17].Position = topRightBack;

      vertices[12].texCoord = new Vector2(0.0f, 0.0f);
      vertices[13].texCoord = new Vector2(0.0f, 1.0f);
      vertices[14].texCoord = new Vector2(1.0f, 1.0f);
      vertices[15].texCoord = new Vector2(0.0f, 1.0f);
      vertices[16].texCoord = new Vector2(1.0f, 1.0f);
      vertices[17].texCoord = new Vector2(0.0f, 0.0f);



      // Add the vertices for the BOTTOM face. 
      vertices[18].Position = btmLeftFront;
      vertices[19].Position = btmLeftBack;
      vertices[20].Position = btmRightBack;
      vertices[21].Position = btmLeftFront;
      vertices[22].Position = btmRightBack;
      vertices[23].Position = btmRightFront;

      vertices[18].texCoord = new Vector2(0.0f, 0.0f);
      vertices[19].texCoord = new Vector2(0.0f, 1.0f);
      vertices[20].texCoord = new Vector2(1.0f, 1.0f);
      vertices[21].texCoord = new Vector2(0.0f, 1.0f);
      vertices[22].texCoord = new Vector2(1.0f, 1.0f);
      vertices[23].texCoord = new Vector2(0.0f, 0.0f);



      // Add the vertices for the LEFT face.
      vertices[24].Position = topLeftFront;
      vertices[25].Position = btmLeftBack;
      vertices[26].Position = btmLeftFront;
      vertices[27].Position = topLeftBack;
      vertices[28].Position = btmLeftBack;
      vertices[29].Position = topLeftFront;

      vertices[24].texCoord = new Vector2(0.0f, 0.0f);
      vertices[25].texCoord = new Vector2(0.0f, 1.0f);
      vertices[26].texCoord = new Vector2(1.0f, 1.0f);
      vertices[27].texCoord = new Vector2(0.0f, 1.0f);
      vertices[28].texCoord = new Vector2(1.0f, 1.0f);
      vertices[29].texCoord = new Vector2(0.0f, 0.0f);



      // Add the vertices for the RIGHT face. 
      vertices[30].Position = topRightFront;
      vertices[31].Position = btmRightFront;
      vertices[32].Position = btmRightBack;
      vertices[33].Position = topRightBack;
      vertices[34].Position = topRightFront;
      vertices[35].Position = btmRightBack;

      vertices[30].texCoord = new Vector2(0.0f, 0.0f);
      vertices[31].texCoord = new Vector2(0.0f, 1.0f);
      vertices[32].texCoord = new Vector2(1.0f, 1.0f);
      vertices[33].texCoord = new Vector2(0.0f, 1.0f);
      vertices[34].texCoord = new Vector2(1.0f, 1.0f);
      vertices[35].texCoord = new Vector2(0.0f, 0.0f);
    }

    private void createIndices()
    {
      indices = new short[nindices];
      for (int i = 0; i < nindices; i++)
        indices[i] = (short)i;
    }

  }

  public class Sphere : Shape
  {

    float radius;


    private void create(Vector3 center, float radius, GraphicsDevice graphics, Effect effect, Texture2D texture, TextureCube skyBox)
    {
      this.createShadowMap = false;
      this.radius = radius;
      this.origin = center;
      this.rotating = false;
      this.worldMatrix = Matrix.Identity;
      this.angle = 0.0f;
      this.texture = texture;
      this.skyBox = skyBox;
      graphicd = graphics;
      this.effect = effect;
      nvertices = 90 * 90;
      nindices = 90 * 90 * 6;
      vbuffer = new VertexBuffer(graphics, typeof(VertexPositionTextureNormal), nvertices, BufferUsage.WriteOnly);
      ibuffer = new IndexBuffer(graphics, IndexElementSize.SixteenBits, nindices, BufferUsage.WriteOnly);
      createIndices();
      createVertices();
      createNormals();
      vbuffer.SetData<VertexPositionTextureNormal>(vertices);
      ibuffer.SetData<short>(indices);
    }

    public Sphere(Vector3 center, float radius, GraphicsDevice graphics, Effect effect, Texture2D texture, TextureCube skyBox)
    {
      create(center, radius, graphics, effect, texture, skyBox);
    }

    public Sphere(Vector3 center, float radius, GraphicsDevice graphics, Effect basicEffect, Effect lightingEffect, Texture2D texture, TextureCube skyBox)
    {
      create(center, radius, graphics, basicEffect, texture, skyBox);
    }

    private void createVertices()
    {
        vertices = new VertexPositionTextureNormal[nvertices];
        Vector3 center = this.origin;
        Vector3 rad = new Vector3((float)Math.Abs(radius), 0, 0);
        for (int x = 0; x < 90; x++) //90 circles, difference between each is 4 degrees
        {
          float difx = 360.0f / 90.0f;
          for (int y = 0; y < 90; y++) //90 veritces, difference between each is 4 degrees 
          {
            float dify = 360.0f / 90.0f;
            Matrix zrot = Matrix.CreateRotationZ(MathHelper.ToRadians(y * dify));
            Matrix yrot = Matrix.CreateRotationY(MathHelper.ToRadians(x * difx));
            Vector3 point = Vector3.Transform(Vector3.Transform(rad, zrot), yrot);
            point = Vector3.Transform(point, Matrix.CreateTranslation(center));
            vertices[x + y * 90].Position = point; 
            vertices[x + y * 90].texCoord = new Vector2(x * difx / 360.0f, y * dify / 360.0f);
            vertices[x + y * 90].Normal = Vector3.Backward;
          }
        }
    }

    private void createIndices()
    {
      indices = new short[nindices];
      int i = 0;
      for (int x = 0; x < 90; x++)
      {
        for (int y = 0; y < 90; y++)
        {
          int s1 = x == 89 ? 0 : x + 1;
          int s2 = y == 89 ? 0 : y + 1;
          short upperLeft = (short)(x * 90 + y);
          short upperRight = (short)(s1 * 90 + y);
          short lowerLeft = (short)(x * 90 + s2);
          short lowerRight = (short)(s1 * 90 + s2);
          indices[i++] = upperLeft;
          indices[i++] = upperRight;
          indices[i++] = lowerLeft;
          indices[i++] = lowerLeft;
          indices[i++] = upperRight;
          indices[i++] = lowerRight;
        }
      }
    }

    public override void rotate()
    {
      const float ROTATION_SPEED = 0.03f;
      angle += ROTATION_SPEED;
      this.worldMatrix = Matrix.CreateRotationY(angle);
    }
  }

  public class Plane : Shape
  {
    float length;

    private void create(Vector3 origin, float length, GraphicsDevice graphics, Effect effect, Texture2D texture, TextureCube skybox)
    {
      this.createShadowMap = false;
      this.length = length;
      this.origin = origin;
      this.rotating = false;
      this.worldMatrix = Matrix.Identity;
      this.angle = 0.0f;
      this.texture = texture;
      this.skyBox = skybox;
      graphicd = graphics;
      this.effect = effect;
      nvertices = 4;
      nindices = 6;
      vbuffer = new VertexBuffer(graphics, typeof(VertexPositionTextureNormal), nvertices, BufferUsage.WriteOnly);
      ibuffer = new IndexBuffer(graphics, IndexElementSize.SixteenBits, nindices, BufferUsage.WriteOnly);
      createIndices();
      createVertices();
      createNormals();
      vbuffer.SetData<VertexPositionTextureNormal>(vertices);
      ibuffer.SetData<short>(indices);
    }

    public Plane(Vector3 origin, float length, GraphicsDevice graphics, Effect effect, Texture2D texture, TextureCube cube)
    {
      create(origin, length, graphics, effect, texture, cube);
    }

    public Plane(Vector3 origin, float length, GraphicsDevice graphics, Effect basicEffect, Effect lightingEffect, Color color, Texture2D texture)
    {
      create(origin, length, graphics, basicEffect, texture, null);
    }

    private void createVertices()
    {

      vertices = new VertexPositionTextureNormal[nvertices];
      for (int i = 0; i < vertices.Length; i++)
      {
        vertices[i].texCoord = new Vector2(0.0f, 0.0f);
        vertices[i].Normal = Vector3.Backward;
      }

      Vector3 leftFront = origin + new Vector3(-1.0f, 0.0f, -1.0f) * length;
      Vector3 leftBack = origin + new Vector3(-1.0f, 0.0f, 1.0f) * length;
      Vector3 rightFront = origin + new Vector3(1.0f, 0.0f, -1.0f) * length;
      Vector3 rightBack = origin + new Vector3(1.0f, 0.0f, 1.0f) * length;

      vertices[0].Position = leftFront;
      vertices[1].Position = leftBack;
      vertices[2].Position = rightFront;
      vertices[3].Position = rightBack;

      vertices[0].texCoord = new Vector2(0.0f, 0.0f);
      vertices[1].texCoord = new Vector2(0.0f, 1.0f);
      vertices[2].texCoord = new Vector2(1.0f, 0.0f);
      vertices[3].texCoord = new Vector2(1.0f, 1.0f);
    }

    private void createIndices()
    {
      indices = new short[nindices];
      indices[0] = 2;
      indices[1] = 1;
      indices[2] = 0;
      indices[3] = 3;
      indices[4] = 1;
      indices[5] = 2;
    }

    public override void rotate()
    {
    }
  }
}
